import iconv from 'iconv-lite'

// CRC16校验计算
export function CRC16(data) {
    let crc = 0xFFFF;

    for (let i = 0; i < data.length; i++) {
        crc ^= data[i];
        for (let j = 0; j < 8; j++) {
            const tmp = crc & 0x0001;
            crc >>= 1;
            if (tmp === 1) {
                crc ^= 0xA001;
            }
        }
    }

    return crc;
}

// ASCII 编码（仅支持 0-127，超出部分会截断）
export function stringToAsciiBytes(str) {
    const bytes = [];
    for (let i = 0; i < str.length; i++) {
        bytes.push(str.charCodeAt(i) & 0xFF); // 取低8位
    }
    return bytes;
}
/**
 * 将 ASCII 字节数组转换回字符串
 * @param {number[]} bytes - ASCII 字节数组（数值范围 0-255）
 * @returns {string} 转换后的字符串
 */
export function asciiBytesToString(bytes) {
    if (!Array.isArray(bytes)) {
        throw new TypeError('Input must be an array');
    }

    return String.fromCharCode(...bytes);
}

/**
 * 将字符串转换成字节数组
 * @param {Object} str 字符串
 */
export function stringToUtf8Bytes(str) {
    // UTF-8 编码（类似URL编码）
    let encodedStr = new String(encodeURIComponent(str)) // "%E4%BD%A0%E5%A5%BD"
    let filterStrs = []
    for (let i = 0; i < encodedStr.length; i++) {
        if (encodedStr.charAt(i) == "%") {
            continue
        }
        filterStrs.push(encodedStr.charAt(i))
    }
    return hexStr2Bytes(filterStrs.join(""))
}
export function stringToGB2312Bytes(str) {
    const gbkBuffer = iconv.encode(str, 'GB2312')
    return Uint8Array.from(gbkBuffer)
}
/**
 * 将字节数组转换回字符串
 * @param {Object} bytes
 */
export function utf8BytesToString(bytes) {
    let hexStr = bytes2HexStr(bytes)
    let hexStrList = []
    for (let i = 0; i < hexStr.length;) {
        hexStrList.push(hexStr.slice(i, i+2))
        i += 2
    }
    // UTF-8 解码
    let decodedStr = decodeURIComponent("%" + hexStrList.join("%")); // "%E4%BD%A0%E5%A5%BD"
    return decodedStr
}
export function gb2312BytesToString(bytes) {
    const text = iconv.decode(Buffer.from(bytes), 'GB2312');
    return text
}

// 十六进制字符串 → 字节数组
export function hexStr2Bytes(hexStr) {
    const cleanHex = hexStr.replace(/\s+/g, '').replace(/^0x/, '');
    if (cleanHex.length % 2 !== 0) {
        throw new Error("Invalid hex string: length must be even");
    }

    const bytes = [];
    for (let i = 0; i < cleanHex.length; i += 2) {
        bytes.push(parseInt(cleanHex.substr(i, 2), 16));
    }
    return new Uint8Array(bytes);
}
// 字节数组 → 十六进制字符串
export function bytes2HexStr(bytes) {
    let hexStr = '';
    for (const byte of bytes) {
        hexStr += byte.toString(16).padStart(2, '0');
    }
    return hexStr;
}

/**
 * 将字节数组转换为整数（支持大端序/小端序）
 * @param {Uint8Array | number[]} bytes - 字节数组
 * @param {boolean} [littleEndian=false] - 是否小端序（默认大端序）
 * @param {boolean} [signed=false] - 是否解析为有符号整数（默认无符号）
 * @returns {number | bigint} - 转换后的整数（超出安全范围时返回 BigInt）
 */
export function bytesToInt(bytes, signed = false, littleEndian = false) {
    // 参数校验
    if (!Array.isArray(bytes) && !(bytes instanceof Uint8Array)) {
        throw new TypeError('Input must be a Uint8Array or number[]');
    }
    if (bytes.length === 0 || bytes.length > 8) {
        throw new RangeError('Bytes length must be between 1 and 8');
    }

    // 检查字节值是否合法（0~255）
    for (const byte of bytes) {
        if (byte < 0 || byte > 255 || !Number.isInteger(byte)) {
            throw new RangeError('Each byte must be an integer in [0, 255]');
        }
    }

    // 处理字节序
    const byteArray = littleEndian ? [...bytes].reverse() : [...bytes];
    let result = 0n; // 使用 BigInt 避免溢出

    for (const byte of byteArray) {
        result = (result << 8n) | BigInt(byte);
    }

    // 有符号数处理（补码转换）
    if (signed) {
        const bitWidth = BigInt(bytes.length * 8);
        const signBit = 1n << (bitWidth - 1n);
        if (result & signBit) {
            result = result - (1n << bitWidth);
        }
    }

    // 返回 Number 或 BigInt
    return result <= Number.MAX_SAFE_INTEGER && result >= Number.MIN_SAFE_INTEGER ?
        signed ? Number(result) : Number(result) >>> 0 :
        result;
}
/**
 * 将整数转换为字节数组
 * @param {number | bigint} value - 要转换的整数
 * @param {number} byteLength - 字节数组长度（1~8）
 * @param {boolean} [littleEndian=false] - 是否小端序（默认大端序）
 * @param {boolean} [signed=false] - 是否处理为有符号整数（默认无符号）
 * @returns {number[]} - 字节数组
 */
export function intToBytes(value, byteLength, signed = false, littleEndian = false) {
    // 参数校验
    if (typeof value !== 'number' && typeof value !== 'bigint') {
        throw new TypeError('Value must be a number or BigInt');
    }
    if (byteLength < 1 || byteLength > 8) {
        throw new RangeError('Byte length must be between 1 and 8');
    }

    // 统一转换为 BigInt 处理
    let bigIntValue;
    if (typeof value === 'number') {
        bigIntValue = BigInt(value);
    } else {
        bigIntValue = value;
    }

    // 处理有符号数（转换为无符号形式）
    let unsignedValue;
    if (signed) {
        const bitWidth = BigInt(byteLength * 8);
        const maxValue = (1n << (bitWidth - 1n)) - 1n;
        const minValue = -(1n << (bitWidth - 1n));
        if (bigIntValue > maxValue || bigIntValue < minValue) {
            throw new RangeError('Value out of range for signed integer');
        }
        unsignedValue = bigIntValue & ((1n << bitWidth) - 1n);
    } else {
        unsignedValue = bigIntValue;
    }

    // 转换为字节数组
    const bytes = [];
    for (let i = 0; i < byteLength; i++) {
        const shift = 8n * BigInt(byteLength - 1 - i);
        const byte = Number((unsignedValue >> shift) & 0xFFn);
        bytes.push(byte);
    }

    // 处理字节序
    return littleEndian ? bytes.reverse() : bytes;
}

// 生成UUID简化版本
export function simpleUUID() {
    return Math.random().toString(36).substring(2) +
        Date.now().toString(36);
}

/**
 * 设备协议报文帧格式
 */
export class DevicePacket {
    // 起始标识，固定为0x7e
    start = 0x7e
    // 协议版本号，从20251024号开始升级为V2版本
    version = 0x02
    // 主控设备sn
    sn = ""
    // 保留，默认0x0
    reserve‌ = 0x00
    // 消息发送序列值，初始值为1，以后递增
    seq = 0
    // 消息类型
    msgId = 0x00
    // 消息方向，服务App发给设备为1，设备发给App为2
    direction = 0x01
    // 数据长度
    len = 0
    // 消息体，可以为null
    msgBody = null
    // CRC校验值
    crc = 0x0000
    // 结束标识，固定为0x7e
    end = 0x7e
    
    constructor() {}
    
    /**
     * 设置下发给设备消息的信息实体
     * @param {Number} seq
     * @param {DeviceMessageSend} deviceMessageSend
     */
    setSendMsgInfo(seq, deviceMessageSend) {
        this.reserve = 0x00
        this.direction = 0x01
        this.seq = seq
        this.sn = deviceMessageSend.sn
        this.msgId = deviceMessageSend.msgId
        this.msgBody = deviceMessageSend.msgBody
    }
}

/**
 * 协议帧编解码器
 */
class DevicePacketCoder {
    /**
     * 编码，将 DevicePacket 对象转换成byte数组
     * @param {DevicePacket} packet
     */
    encode(packet) {
        console.log("准备对消息实体进行编码：", packet)
        let byteArray = []

        // 起始标识
        byteArray.push(packet.start)
        
        // 协议版本号
        byteArray.push(packet.version)

        // 主控设备sn，不足位数则高位补0xFF
        let snBytesArray = stringToAsciiBytes(packet.sn)
        let realSnBytes = []
        if (snBytesArray.length < 15) {
            for (let i = 0; i < (15 - snBytesArray.length); i++) {
                realSnBytes.push(0xFF)
            }
        }
        realSnBytes.push(...snBytesArray)
        byteArray.push(...realSnBytes)

        // 保留，如数据域加密标识等。默认0x00。
        byteArray.push(packet.reserve)

        // 消息发送序列值，初始值为1，以后递增
        byteArray.push(packet.seq)

        // 命令消息类型
        byteArray.push(packet.msgId)

        // 命令方向：服务App发给设备为1，设备发给App为2
        byteArray.push(packet.direction)

        // 编码消息数据内容
        let msgBodyBytes = []
        if (packet.msgBody != null) {
            msgBodyBytes = packet.msgBody.encode()
            console.log("消息内容体编码的字节流为：", msgBodyBytes)
        }
        packet.len = msgBodyBytes.length
        
        // 数据的总长度，若无数据则值为0
        let lenByteData = intToBytes(packet.len, 2)
        byteArray.push(...lenByteData)
        
        // 消息数据内容(≤2048）
        if (packet.len > 0) {
            byteArray.push(...msgBodyBytes)
        }

        // 对消息体进行crc16校验
        let crcCalcByteData = byteArray.slice(1) // 起始标识不用参与CRC校验计算
        let crc = CRC16(crcCalcByteData)
        byteArray.push(...intToBytes(crc, 2))

        // 结束标识
        byteArray.push(packet.end)
        
        // 转义
        byteArray = this.#escape(byteArray)
        
        // 返回字节数组
        return byteArray
    }

    /**
     * 解码，将byte数组转换成 DevicePacket 对象
     * @param {Array} byteArray
     */
    decode(byteArray) {
        console.log("准备对消息协议帧字节流进行解码：", byteArray)
        // 先反转义，还原原本byte数据流
        let byteData = this.#unescape(byteArray)
        if (byteData == null || byteData.length == 0) {
            console.log("byte数组为空")
            return null
        }
        let packet = new DevicePacket()

        // 起始标识
        packet.start = byteData[0]
        // 协议版本号
        packet.version = byteData[1]

        // 主控设备sn，不足位数则高位补0xFF
        let snBytesArray = byteData.slice(2, 17)
        let realSnBytes = []
        for (let i = 0; i < snBytesArray.length; i++) {
            if (snBytesArray[i] == 0xFF) {
                continue
            }
            realSnBytes.push(snBytesArray[i])
        }
        packet.sn = asciiBytesToString(realSnBytes)

        // 保留，如数据域加密标识等。默认0x0。
        packet.reserve = byteData[17]

        // 消息发送序列值，初始值为1，以后递增
        packet.seq = bytesToInt([byteData[18]])

        // 命令消息类型
        packet.msgId = byteData[19]

        // 命令方向：服务App发给设备为1，设备发给App为2
        packet.direction = byteData[20]

        // 数据的总长度，若无数据则值为0
        let lenByteData = byteData.slice(21, 23)
        packet.len = bytesToInt(lenByteData)

        // 消息数据内容(≤2048）
        let idx = 23 + packet.len
        if (packet.len > 0) {
            let msgBodyBytes = byteData.slice(23, 23 + packet.len)
            // 根据消息ID进行针对性解析，不同的消息类型，body内容不一样
            packet.msgBody = this.#parseMsgBody(packet.msgId, msgBodyBytes)
        }

        // 对消息体进行crc16校验
        packet.crc = bytesToInt(byteData.slice(idx, idx + 2))
        idx += 2

        // 结束标识
        packet.end = byteData[idx]

        // 校验帧格式是否有问题
        if ((idx + 1) != byteData.length) {
            console.log("帧格式错误")
            return null
        }
        let crcCalcByteData = byteData.slice(1,-3) // 去掉起始标志，以及CRC校验位，截止标志
        let calcCrc = CRC16(crcCalcByteData)
        console.log("计算CRC的值:", bytes2HexStr(intToBytes(calcCrc, 2)))
        // 调试阶段，先关闭CRC校验
        // if (packet.crc != calcCrc) {
        //     console.log("帧格式错误")
        //     return null
        // }
        console.log("帧格式校验成功")

        return packet
    }

    #parseMsgBody(msgId, msgBodyBytes) {
        console.log("解码消息体内容：", "msgid: ", msgId, "body byte data: ", bytes2HexStr(msgBodyBytes))
        let msgBodyEntity = null
        
        switch (msgId) {
            case MsgType.DeviceBasicInfoReport:
                msgBodyEntity = new DeviceBasicInfoReportMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.StatusSelfCheckReport:
                msgBodyEntity = new StatusSelfCheckReportMsgBody([])
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.PeripheralStatusReport:
                msgBodyEntity = new StatusMonitorReportMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.DeviceStatusReport:
                msgBodyEntity = new DeviceWorkStatusReportMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.Heartbeat:
                msgBodyEntity = new HearbeatRequestMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.RecipeSend:
            case MsgType.RecipeStartCooking:
            case MsgType.ElectronicScaleFoodPreparation:
            case MsgType.ElectronicScaleControlSetEmptyBoxWeight:
            case MsgType.ElectronicScaleControlSetWeightUnit:
            case MsgType.RangeHoodFanControl:
            case MsgType.RangeHoodPowerControl:
            case MsgType.RecipePauseCooking:
            case MsgType.RecipeResumeCooking:
            case MsgType.RecipeStopCooking:
            case MsgType.RecipeStartPlating:
            case MsgType.RecipeFinishPlating:
            case MsgType.ManualStartWashPot:
            case MsgType.ManualFinishWashPot:
            case MsgType.ManualShutdown:
            case MsgType.RecipeStartAutoWashPot:
            case MsgType.ManualAddSeasoning:
            case MsgType.SettingDeviceCountLimit:
            case MsgType.SettingFactoryReset:
            case MsgType.EmergencyOperation:
                msgBodyEntity = new CommonConfirmReplyMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.RecipeCookingStatus:
                msgBodyEntity = new RecipeCookingStatusMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            
            case MsgType.RecipeFinishCooking:
            case MsgType.RecipeFinishAutoWashPot:
                msgBodyEntity = new DeviceWorkStatusMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.ManualAddSeasoningResult:
                msgBodyEntity = new ManualAddSeasoningResultMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.SettingSeasoningRatioCalibration:
            case MsgType.SettingSeasoningRatioClean:
                msgBodyEntity = new SeasoningRatioCalibrationResultMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
            case MsgType.SettingSeasoningBaseUnit:
                msgBodyEntity = new SettingSeasoningBaseUnitResultMsgBody()
                msgBodyEntity.decode(msgBodyBytes)
                break
        }
        
        return msgBodyEntity
    }

    /**
     * 起止符以外若出现0x7e 0x7d则要进行转义处理，转义规则定义如下:
     * 0x7e<---->0x7d 后紧跟一个0x02
     * 0x7dㄑ---->0x7d 后紧跟一个0x01
     */
    // 转义
    #escape(byteArray) {
        console.log("准备对报文进行转义:", bytes2HexStr(byteArray))
        // 首尾0x7e不用转
        let contentBytes = byteArray.slice(1,-1)
        let resultBytes = []
        // 开始符号
        resultBytes.push(byteArray[0])
        // 转义内容
        for(let idx = 0; idx < contentBytes.length;) {
            let byteItem = contentBytes[idx]
            if (byteItem == 0x7e) {
                resultBytes.push(0x7d)
                resultBytes.push(0x02)
            } else if (byteItem == 0x7d) {
                resultBytes.push(0x7d)
                resultBytes.push(0x01)
            } else {
                // 无需转义
                resultBytes.push(byteItem)
            }
            idx += 1
        }
        // 结束符号
        resultBytes.push(byteArray[byteArray.length-1])
        console.log("转义后的报文:", bytes2HexStr(resultBytes))
        return resultBytes
    }
    // 反转义
    #unescape(byteArray) {
        console.log("准备对报文进行反转义:", bytes2HexStr(byteArray))
        // 首尾0x7e不用转
        let contentBytes = byteArray.slice(1,-1)
        let resultBytes = []
        // 开始符号
        resultBytes.push(byteArray[0])
        // 反转义内容
        for(let idx = 0; idx < contentBytes.length;) {
            let byteItem = contentBytes[idx]
            if (byteItem == 0x7d) {
                idx += 1
                let nextByteItem = contentBytes[idx]
                if (nextByteItem == 0x01) {
                    resultBytes.push(0x7d)
                } else if (nextByteItem == 0x02) {
                    resultBytes.push(0x7e)
                } else {
                    console.log("反转义的字节流不合法")
                }
            } else {
                // 无需转义
                resultBytes.push(byteItem)
            }
            idx += 1
        }
        // 结束符号
        resultBytes.push(byteArray[byteArray.length-1])
        console.log("反转义后的报文:", bytes2HexStr(resultBytes))
        return resultBytes
    }
}
export const DevicePacketCoderUtil = new DevicePacketCoder()


/**
 * 发送设备的消息
 */
export class DeviceMessageSend {
    // 设备sn
    sn = ""
    // 消息类型，MsgIdType 类型
    msgId = 0x00
    // 消息体，可以为null
    msgBody = null
    // seq序列编号
    seq = 0x00

    constructor(sn, msgId, msgBody, seq = 0) {
        this.sn = sn
        this.msgId = msgId
        this.msgBody = msgBody
        this.seq = seq
        if (this.seq >= 0xff) {
          this.seq = 1
        }
    }
}

/**
 * 接收设备的消息
 */
export class DeviceMessageReceive {
    // 设备sn
    sn = ""
    // 消息类型，MsgIdType 类型
    msgId = 0x00
    // 消息体，可以为null
    msgBody = null
    // seq序列编号
    seq = 0x00

    constructor(sn, msgId, msgBody, seq = 0) {
        this.sn = sn
        this.msgId = msgId
        this.msgBody = msgBody
        this.seq = seq
        if (this.seq >= 0xff) {
          this.seq = 1
        }
    }
}

//////////////////////////// 消息类型对应的消息实体定义 /////////////////////////////

/**
 * 设备消息类别
 */
export const MsgType = {
    // 发送菜谱
    RecipeSend: 0x20,
    // 开始炒菜
    RecipeStartCooking: 0x21,
    // 炒菜进展
    RecipeCookingStatus: 0x22,
    // 暂停炒菜
    RecipePauseCooking: 0x23,
    // 继续炒菜
    RecipeResumeCooking: 0x24,
    // 终止炒菜
    RecipeStopCooking: 0x25,
    // 完成炒菜
    RecipeFinishCooking: 0x26,
    // 开始出菜
    RecipeStartPlating: 0x27,
    // 完成出菜
    RecipeFinishPlating: 0x28,
    // 开始自动洗锅
    RecipeStartAutoWashPot: 0x29,
    // 完成自动洗锅
    RecipeFinishAutoWashPot: 0x2A,
    
    // 关机
    ManualShutdown: 0x30,
    // 开始手动洗锅
    ManualStartWashPot: 0x31,
    // 完成手动洗锅
    ManualFinishWashPot: 0x32,
    // 外设操作（调料投放）
    ManualAddSeasoning: 0x33,
    // 外设操作结果上报
    ManualAddSeasoningResult: 0x34,
    
    // 投料系数校准
    SettingSeasoningRatioCalibration: 0x40,
    // 投料系数清空
    SettingSeasoningRatioClean: 0x40,
    // 默认投料基准单位
    SettingSeasoningBaseUnit: 0x41,
    // 限制使用
    SettingDeviceCountLimit: 0x42,
    // 恢复出厂
    SettingFactoryReset: 0x43,
    
    // 基础信息上报
    DeviceBasicInfoReport: 0x50,
    // 自检上报
    StatusSelfCheckReport: 0x51,
    // (外设)监测上报
    PeripheralStatusReport: 0x52,
    // 紧急处理
    EmergencyOperation: 0x53,
    // 工作状态上报
    DeviceStatusReport: 0x54,
    // 心跳
    Heartbeat: 0x55,
    
    // 油烟机控制-风扇与照明灯控制
    RangeHoodFanControl: 0x70,
    // 油烟机控制-关闭油烟机
    RangeHoodPowerControl: 0x71,
    
    // 电子秤控制-显示食材信息
    ElectronicScaleFoodPreparation: 0x80,
    // 电子秤控制-设置食材盒空重
    ElectronicScaleControlSetEmptyBoxWeight: 0x81,
    // 电子秤控制-设置食材盒空重
    ElectronicScaleControlSetWeightUnit: 0x82,
}

/**
 * 设备工作状态
 */
export const WorkStatus = {
    // 错误
    Error: 0x00,
    // 空闲状态
    Idle: 0xFF,
    // 炒菜状态
    Cooking: 0x01,
    // 暂停状态
    Pause: 0x02,
    // 等待出菜状态
    WaitPlating: 0x03,
    // 正在出菜状态
    Plating: 0x04,
    // 自动洗锅状态
    AutoWashPot: 0x05,
    // 手动洗锅状态
    ManualWashPot: 0x06,
    // 关机状态
    Shutdown: 0x07,
    // 离线状态
    Offline: 0x08,
}

// 默认回复报文，APP发送给设备，或者设备发送给APP，确认回复的结果
export class CommonConfirmReplyMsgBody {
    // 结果，1-Byte，数字，不同报文含义不一样
    result = 0
    // 状态值，不同报文含义不一样
    status = 0

    constructor(result, status = 0) {
        this.result = result
        this.status = status
    }
    
    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        if (!Array.isArray(byteArray) && !(byteArray instanceof Uint8Array)) {
            throw new Error('参数必须是字节数组');
        }
        if (byteArray.length === 0) {
            throw new Error('字节数组不能为空');
        }
        if (byteArray.length < 2) {
            throw new Error('字节数组长度不正确');
        }
        this.result = bytesToInt([byteArray[0]])
        this.status = bytesToInt([byteArray[1]])
    }
    
    /**
     * 编码，将对象信息编码成字节数组，并返回
     */
    encode() {
        if (this.result == null) { // 空消息体
            return []
        }
        let byteArray = []
        byteArray.push(...intToBytes(this.result, 1))
        byteArray.push(...intToBytes(this.status, 1))
        return byteArray
    }
}

// 设备消息-基础信息上报
export class DeviceBasicInfoReportMsgBody {
    // 设备型号
    model
    // 炒菜机的限制状态
    limit
    // 主控板固件版本号
    mfir1mver
    // 驱动板固件版本号
    mfir2mver
    
    constructor() {
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        if (!Array.isArray(byteArray) && !(byteArray instanceof Uint8Array)) {
            throw new Error('参数必须是字节数组');
        }
        if (byteArray.length === 0) {
            throw new Error('字节数组不能为空');
        }
        
        this.limit = bytesToInt(byteArray.slice(0, 1))
        
        let modelLen = bytesToInt(byteArray.slice(1, 2))
        let idx = 2
        this.model = asciiBytesToString(byteArray.slice(idx, idx + modelLen))
        idx += modelLen

        let mfir1len = bytesToInt(byteArray.slice(idx, idx+1))
        idx += 1
        if (mfir1len > 0) {
            this.mfir1mver = asciiBytesToString(byteArray.slice(idx, idx + mfir1len))
            idx += mfir1len
        }
        let mfir2len = bytesToInt(byteArray.slice(idx, idx+1))
        idx += 1
        if (mfir2len > 0) {
            this.mfir2mver = asciiBytesToString(byteArray.slice(idx, idx + mfir2len))
        }
    }
}
// 设备消息-自检上报
export class StatusSelfCheckReportMsgBody {
    // 设备信息，每一个都是一个 VesselInfo 类型
    itemList = []

    constructor(itemList) {
        this.itemList = itemList
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        if (!Array.isArray(byteArray) && !(byteArray instanceof Uint8Array)) {
            throw new Error('参数必须是字节数组');
        }
        if (byteArray.length === 0) {
            throw new Error('字节数组不能为空');
        }
        this.itemList = []
        for (let idx = 0; idx < byteArray.length;) {
            let item = new VesselInfo()
            item.targetId = bytesToInt(byteArray.slice(idx, idx + 2))
            idx += 2

            item.result = bytesToInt(byteArray.slice(idx, idx + 1))
            idx += 1

            this.itemList.push(item)
        }
    }
}

// 外设容器的信息，例如：料盒
export class VesselInfo {
    // 设备地址
    targetId = 0x0000
    // 设备状态
    result = 0
}

// 设备消息-监测上报
export class StatusMonitorReportMsgBody {
    // 设备信息，每一个元素都是一个 StatusMonitorInfo 类型
    itemList = []

    constructor(itemList) {
        this.itemList = itemList
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        if (!Array.isArray(byteArray) && !(byteArray instanceof Uint8Array)) {
            throw new Error('参数必须是字节数组');
        }
        if (byteArray.length === 0) {
            throw new Error('字节数组不能为空');
        }
        this.itemList = []
        for (let idx = 0; idx < byteArray.length;) {
            let item = new StatusMonitorInfo()
            item.targetId = bytesToInt(byteArray.slice(idx, idx + 2))
            idx += 2

            item.result = bytesToInt(byteArray.slice(idx, idx + 1))
            idx += 1
            
            item.status = bytesToInt(byteArray.slice(idx, idx + 2))
            idx += 2

            this.itemList.push(item)
        }
    }
}
// 状态监测项信息
export class StatusMonitorInfo {
    // 设备地址
    targetId = 0x0000
    // 设备状态
    result = 0x00
    // 设备状态信息，不同外设含义不一样
    status = 0x0000
}
// 设备消息-工作状态上报
export class DeviceWorkStatusReportMsgBody {
    // 工作状态
    work = 0x00
    // 上报时已开机运行时间（秒）
    runtime = 0x00000000

    constructor(work, runtime) {
        this.work = work
        this.runtime = runtime
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        if (!Array.isArray(byteArray) && !(byteArray instanceof Uint8Array)) {
            throw new Error('参数必须是字节数组');
        }
        if (byteArray.length === 0) {
            throw new Error('字节数组不能为空');
        }
        this.work = bytesToInt(byteArray.slice(0, 1))
        this.runtime = bytesToInt(byteArray.slice(1, 5))
    }
}
// 设备消息-心跳上报
export class HearbeatRequestMsgBody {
    // 工作状态
    req = 0x00

    constructor(req) {
        this.req = req
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        if (!Array.isArray(byteArray) && !(byteArray instanceof Uint8Array)) {
            throw new Error('参数必须是字节数组');
        }
        if (byteArray.length === 0) {
            throw new Error('字节数组不能为空');
        }
        this.req = bytesToInt(byteArray.slice(0, 1))
    }
}
// APP消息-心跳回复
export class HearbeatResponseMsgBody {
    // 设备地址
    rsp = 0x00
    
    constructor(rsp) {
        this.rsp = rsp
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        byteArray.push(...intToBytes(this.rsp, 1))
        return byteArray
    }
}

// APP消息-发送菜谱
// App将菜谱数据转化为对应炒菜机的设备地址操作序列
export class RecipeSendMsgBody {
    // 菜谱ID
    nameId
    // 执行步骤，每一个元素都是一个 RecipeStepInfo 类型
    itemList = []

    constructor(nameId, itemList) {
        this.nameId = nameId
        this.itemList = itemList
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []

        // 名称
        byteArray.push(...stringToAsciiBytes(this.nameId))
        
        // 步骤
        for (let idx = 0; idx < this.itemList.length; idx++) {
            let item = this.itemList[idx]
            
            // 步骤时长（秒）
            byteArray.push(...intToBytes(item.timeoffset, 2))
            
            // 设备地址
            byteArray.push(...intToBytes(item.targetId, 2))
            
            // 执行目标
            byteArray.push(...intToBytes(item.goal, 2))
        }
        
        return byteArray
    }
}
// 菜谱步骤项信息
export class RecipeStepInfo {
    // 步骤时长（秒）
    timeoffset = 0x0000
    // 设备地址
    targetId = 0x0000
    // 执行目标，不同设备外设，含义不一样。例如：如果是加热器那含义是加热功率等级，如果是铲子那含义是翻炒次数
    goal = 0x0000
    
    constructor(timeoffset, targetId, goal) {
        this.timeoffset = timeoffset
        this.targetId = targetId
        this.goal = goal
    }
}

// APP消息-开始炒菜
// App向设备发送开始炒菜指令
export class RecipeStartCookingMsgBody {
    // 菜谱ID
    nameId

    constructor(nameId) {
        this.nameId = nameId
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []

        // 菜谱ID
        byteArray.push(...stringToAsciiBytes(this.nameId))
        
        return byteArray
    }
}

// 设备消息-炒菜进展，多个命令会用到
// 设备每完成一个步骤就反馈步骤序号
export class RecipeCookingStatusMsgBody {
    // 工作状态
    work = 0x00
    // 当前执行的步骤
    step = 0x00
    // 当前执行的结果
    result = 0x00
    // 状态信息
    status = 0x00

    constructor(work, step, result, status) {
        this.work = work
        this.step = step
        this.result =  result
        this.status = status
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        // 工作状态
        this.work = bytesToInt(byteArray.slice(0, 1))
        // 当前执行的步骤
        this.step = bytesToInt(byteArray.slice(1, 2))
        // 当前执行的结果
        this.result = bytesToInt(byteArray.slice(2, 3))
        // 状态信息
        this.status = bytesToInt(byteArray.slice(3, 4))
    }
}
// 设备消息-设备工作状态，多个命令会用到
export class DeviceWorkStatusMsgBody {
    // 工作状态
    work

    constructor(work) {
        this.work = work
    }

    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        // 工作状态
        this.work = bytesToInt(byteArray.slice(0, 1))
    }
}

// APP消息-参数配置-投料校准
export class SettingSeasoningRatioCalibrationMsgBody {
    /**
     * 校准料盒列表，数据项类型 {@link SeasoningRatioCalibrationInfo}
     */
    itemList

    constructor(itemList) {
        this.itemList = itemList
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        for(let item of this.itemList) {
            // 设备地址
            byteArray.push(...intToBytes(item.targetId, 2))
            // 实际投料比例。小数，高字节为整数部分，低字节为小数部分
            let integerPart = Math.floor(item.ratio)
            let decimalPart = item.ratio - integerPart
            // 整数
            byteArray.push(...intToBytes(integerPart, 1))
            // 只保留两位小数即可
            byteArray.push(...intToBytes(Math.floor(decimalPart * 100), 1))
        }
        return byteArray
    }
}
export class SeasoningRatioCalibrationInfo {
    // 设备地址
    targetId = 0x0000
    // 实际投料比例。小数，高字节为整数部分，低字节为小数部分
    ratio = 0.00
    
    constructor(targetId, ratio) {
        this.targetId = targetId
        this.ratio = ratio
    }
}

// 设备消息-参数配置-校准结果
export class SeasoningRatioCalibrationResultMsgBody {
    /**
     * 校准结果列表，数据项类型 {@link SeasoningRatioCalibrationResultInfo}
     */
    itemList

    constructor(itemList) {
        this.itemList = itemList
    }
    
    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        this.itemList = []
        
        for(let idx = 0; idx < byteArray.length;) {
            let item = new SeasoningRatioCalibrationResultInfo()
            // 设备地址
            item.targetId = bytesToInt(byteArray.slice(idx, idx+2))
            idx += 2
            // 投料校准更新结果
            item.result = bytesToInt(byteArray.slice(idx, idx+1))
            idx += 1
            
            this.itemList.push(item)
        }
    }
}
export class SeasoningRatioCalibrationResultInfo {
    // 设备地址
    targetId = 0x0000
    // 投料校准更新结果
    result = 0
    
    constructor(targetId, result) {
        this.targetId = targetId
        this.result = result
    }
}

// APP消息-参数配置-默认投料基准单位
export class SettingSeasoningBaseUnitMsgBody {
    // 设备地址
    targetId = 0x0000
    // 该调料的默认基准单位
    unitNum = 0x0000
    
    constructor(targetId, unitNum) {
        this.targetId = targetId
        this.unitNum = unitNum
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        // 设备地址
        byteArray.push(...intToBytes(this.targetId, 2))
        // 该调料的默认基准单位
        byteArray.push(...intToBytes(this.unitNum, 2))
        return byteArray
    }
}

// 设备消息-参数配置-默认投料基准单位结果
export class SettingSeasoningBaseUnitResultMsgBody {
    // 设备地址
    targetId = 0x0000
    // 投料校准更新结果
    result = 0
    
    constructor(targetId, result) {
        this.targetId = targetId
        this.result = result
    }
    
    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        // 设备地址
        this.targetId = bytesToInt(byteArray.slice(0, 2))
		// 结果
        this.result = bytesToInt(byteArray.slice(2, 3))
    }
}
export class SeasoningBaseUnitResultInfo {
    // 设备地址
    targetId = 0x0000
    // 投料校准更新结果
    result = 0
    
    constructor(targetId, result) {
        this.targetId = targetId
        this.result = result
    }
}

// APP消息-参数配置-限制使用
export class SettingDeviceCountLimitMsgBody {
    // 限制烹饪次数。范围0~0xFF。0表示不可使用，0xFF为无限制
    limit = 0
    
    constructor(limit) {
        this.limit = limit
    }
    
    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        // 限制烹饪次数
        byteArray.push(...intToBytes(this.limit, 1))
        return byteArray
    }
}

// APP消息-手工操作-调料投放
// App发送某个调料的投放指令，主控板根据指令以及参数执行操作。
export class ManualAddSeasoningMsgBody {
    /**
     * 调料投放列表，数据项类型 {@link SeasoningAddInfo}
     */
    itemList

    constructor(itemList) {
        this.itemList = itemList
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        for(let item of this.itemList) {
            // 设备地址
            byteArray.push(...intToBytes(item.targetId, 2))
            // 执行目标
            byteArray.push(...intToBytes(item.goal, 2))
        }
        return byteArray
    }
}
export class SeasoningAddInfo {
    // 设备地址
    targetId = 0x0000
    // 执行目标，单位为g
    goal = 0x0000
    
    constructor(targetId, goal) {
        this.targetId = targetId
        this.goal = goal
    }
}
// 设备消息-手工操作-调料投放结果
export class ManualAddSeasoningResultMsgBody {
    /**
     * 调料投放结果列表，数据项类型 {@link SeasoningAddResultInfo}
     */
    itemList

    constructor(itemList) {
        this.itemList = itemList
    }
    
    /**
     * 解码，将字节数组解码成对象信息
     * @param {Object} byteArray 字节数组
     */
    decode(byteArray) {
        this.itemList = []
        
        if (byteArray == null || byteArray.length == 0) {
            return
        }
        for(let idx = 0; idx < byteArray.length;) {
            let item = new SeasoningAddResultInfo()
            // 设备地址
            item.targetId = bytesToInt(byteArray.slice(idx, idx+2))
            idx += 2
            // 调料投放结果
            item.result = bytesToInt(byteArray.slice(idx, idx+1))
            idx += 1
            // 状态值
            item.status = bytesToInt(byteArray.slice(idx, idx+2))
            idx += 2
            
            this.itemList.push(item)
        }
    }
}
export class SeasoningAddResultInfo {
    // 设备地址
    targetId = 0x0000
    // 调料投放结果。0 错误；1 正确
    result = 0x0
    // 状态值，不同的外设含义不一样
    status = 0x00
    
    constructor(targetId, result, status) {
        this.targetId = targetId
        this.result = result
        this.status = status
    }
}
// APP消息-紧急处理
export class DeviceEmergencyOperationMsgBody {
    // 设备地址
    targetId = 0x0000
    // 执行目标，单位为g
    goal = 0
    
    constructor(targetId, goal) {
        this.targetId = targetId
        this.goal = goal
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        // 设备地址
        byteArray.push(...intToBytes(this.targetId, 2))
        // 执行目标
        byteArray.push(...intToBytes(this.goal, 1))
        return byteArray
    }
}

// APP消息-电子秤控制-食材准备
// App根据要烹饪的菜谱，将食材发送到电子秤
export class ElectronicScaleFoodPreparationMsgBody {
    // 食材盒1所装食材总重量
    weight1 = 0x0000
    // 食材盒2所装食材总重量
    weight2 = 0x0000
    // 食材盒3所装食材总重量
    weight3 = 0x0000
    // 食材盒4所装食材总重量
    weight4 = 0x0000

    constructor(weight1, weight2, weight3, weight4) {
        this.weight1 = weight1
        this.weight2 = weight2
        this.weight3 = weight3
        this.weight4 = weight4
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []

        // 封装食材重量
        byteArray.push(...intToBytes(this.weight1, 2))
        byteArray.push(...intToBytes(this.weight2, 2))
        byteArray.push(...intToBytes(this.weight3, 2))
        byteArray.push(...intToBytes(this.weight4, 2))
        
        return byteArray
    }
}
// APP消息-电子秤控制-设置食材盒空重
export class ElectronicScaleEmptyBoxWeightMsgBody {
    // 空的食材盒自身重量，单位为克
    weight = 0x0000

    constructor(weight) {
        this.weight = weight
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        byteArray.push(...intToBytes(this.weight, 2))
        return byteArray
    }
}
// APP消息-电子秤控制-设置默认重量显示单位
export class ElectronicScaleSetWeightUnitMsgBody {
    // 默认单位：
    // 0x01：克/g
    // 0x02：英镑
    // 0x03：盎司
    unit = 0x00

    constructor(unit) {
        this.unit = unit
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []
        byteArray.push(...intToBytes(this.unit, 1))
        return byteArray
    }
}
// 食材重量项信息
export class FoodWeightInfo {
    // 食材名称
    name = ""
    // 食材重量，单位g克
    weight = 0x00000000

    constructor(name, weight) {
        this.name = name
        this.weight = weight
    }
}

// APP消息-油烟机-设置风扇灯光
export class RangeHoodFanControlMsgBody {
    // 开关状态
    // powerStatus
    // 风扇转速等级
    fanSpeed
    // 照明灯亮度等级
    lightLevel

    constructor(fanSpeed, lightLevel) {
        // this.powerStatus = powerStatus
        this.fanSpeed = fanSpeed
        this.lightLevel = lightLevel
    }

    /**
     * 编码，将对象信息编码成字节数组
     */
    encode() {
        let byteArray = []

        // 开关状态
        // byteArray.push(...intToBytes(this.powerStatus, 1))
        // 风扇转速等级
        byteArray.push(...intToBytes(this.fanSpeed, 1))
        // 照明灯亮度等级
        byteArray.push(...intToBytes(this.lightLevel, 1))
        
        return byteArray
    }
}